Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #364 +/- ##
==========================================
- Coverage 71.42% 70.73% -0.70%
==========================================
Files 205 207 +2
Lines 14910 15088 +178
==========================================
+ Hits 10650 10672 +22
- Misses 3484 3634 +150
- Partials 776 782 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🔴 Performance DegradationSome benchmarks have degraded compared to the previous run. Show table
|
🔴 Performance DegradationSome benchmarks have degraded compared to the previous run. Show table
|
| // When set, hot_stores, hot_read_stores, write_stores, read_stores must be empty. No bulk; search only. | ||
| Regions struct { | ||
| // UseRegions is whether to use regions or not | ||
| UseRegions bool `config:"use_regions"` |
There was a problem hiding this comment.
currently, config looks like that:
experimental:
regions:
use_regions: true
We repeat regions twice here.
I think it would be cleaner to have just:
experimental:
regions:
enabled: true
|
|
||
| // storeEmulator implements store API (Search, Fetch, Status) so seq-proxy can be | ||
| // queried as a store for aggregation-friendly cross-region queries (e.g. quantiles). | ||
| type storeEmulator struct { |
There was a problem hiding this comment.
For me storeAdapter would be a better name.
| return &storeEmulator{searchIngestor: si} | ||
| } | ||
|
|
||
| func (s *storeEmulator) Search(ctx context.Context, req *storeapi.SearchRequest) (*storeapi.SearchResponse, error) { |
There was a problem hiding this comment.
One thing: explain doesn't work for regions. I set up a simple region cluster and here are the results:
Explained query for regions:
{
"error": {
"code": "ERROR_CODE_NO",
"message": ""
},
"explain": {
"duration": "316.815518ms",
"message": "proxy/ComplexSearch",
"children": [
{
"duration": "206.588436ms",
"message": "proxy/searchShard",
"children": [
{
"duration": "17.383µs",
"message": "Making search request to localhost:9114"
}
]
},
{
"duration": "316.62144ms",
"message": "proxy/searchShard",
"children": [
{
"duration": "48.615µs",
"message": "Making search request to localhost:9124"
}
]
}
]
}
}
Explained query for a single region:
{
"error": {
"code": "ERROR_CODE_NO",
"message": ""
},
"explain": {
"duration": "1.5405ms",
"message": "proxy/ComplexSearch",
"children": [
{
"duration": "1.41697ms",
"message": "proxy/searchShard",
"children": [
{
"duration": "33.876µs",
"message": "Making search request to localhost:9014"
},
{
"duration": "157.948µs",
"message": "store/Search",
"children": [
{
"duration": "17.734µs",
"message": "parse query"
},
{
"duration": "8.126µs",
"message": "search iteratively"
},
{
"duration": "0s",
"message": "search iteratively (cpu time)"
},
{
"duration": "0s",
"message": "waiting goroutines (all cores)"
}
]
}
]
}
]
}
}
| // Regions maps region name to store API gRPC address (e.g. "eu" -> "eu-proxy:9004"). | ||
| Regions map[string]string `config:"regions"` | ||
| // MaxConcurrent limits how many regions are queried in parallel per search (0 = no limit). | ||
| MaxConcurrent int `config:"max_concurrent" default:"0"` |
There was a problem hiding this comment.
I think it's hard to tell what max_concurrent means without reading a code comment.
experimental:
regions:
use_regions: true
...
max_concurrent: 5
There was a problem hiding this comment.
nit: Honestly, I would just remove these Max* options altogether. Yes, we'll have 300+ regions, but it seems like a manageable task to merge the results, especially once we have streaming single-phase search.
| } | ||
|
|
||
| func (s *storeEmulator) Search(ctx context.Context, req *storeapi.SearchRequest) (*storeapi.SearchResponse, error) { | ||
| sr := search.SearchRequestFromStoreAPI(req) |
There was a problem hiding this comment.
Store adapter doesn't bypass store protocol version. It means regions proxy currently get store protocol version as 2 while the primary proxy gets 1. It's possible that proxy can misinterpret QPRs from regions.
| MaxRegexTokensCheck int `config:"max_regex_tokens_check" default:"0"` | ||
|
|
||
| // Regions configures the proxy to query only remote regions (other seq-proxies via store API). | ||
| // When set, hot_stores, hot_read_stores, write_stores, read_stores must be empty. No bulk; search only. |
There was a problem hiding this comment.
nit: Even with proxy setup I still can configure hot_stores:
cluster:
replicas: 1
hot_stores:
- localhost:9004
- localhost:9008
However, I can't use them:
{
"code": 3,
"message": "no regions specified in request (required when using experimental.regions)"
}
I guess, we could validate config that hot_stores can't be set if regions are enabled.
| // RegionStores is a separate topology for experimental.regions (not mixed with hot_stores). When set, search uses only this. | ||
| RegionStores *stores.Stores | ||
| // RegionNames maps shard index to region name (same order as RegionStores.Shards). | ||
| RegionNames []string |
There was a problem hiding this comment.
nit: so we basically have region names decoupled from region stores, but indexes are same. This is a bit ugly. I suppose it's not possible to solve this problem altogether, but I would not be against moving regions inside stores.Stores. Default setup could have empty (not used) region names
There was a problem hiding this comment.
In my opinion, there are quite a few ugly spots in the proxy design right now.
This PR is the first prototype phase of the solution. We plan to introduce unified (non‑experimental) configs for describing cluster topology with region support.
I think it would be good, during the second phase, to also clean up the structures and entities in this part of the project.
| searchStores = si.config.HotReadStores | ||
|
|
||
| var searchStores *stores.Stores | ||
| if config.UseRegions { |
There was a problem hiding this comment.
nit: I'm not against having longer methods. However, here I would really move this logic of determing which stores we call to some method like locateStores or something like that.
| shards := make([][]string, 0) | ||
| vers := make([]string, 0) | ||
| for i, name := range si.config.RegionNames { | ||
| if _, ok := allowed[name]; ok && i < len(si.config.RegionStores.Shards) { |
There was a problem hiding this comment.
nit (not really): would be cool to support wildcards in future. Users could specify regions like krd*
There was a problem hiding this comment.
This is a very, very basic implementation. It's a good idea for a future enhancement.
| // storeEmulator implements store API (Search, Fetch, Status) so seq-proxy can be | ||
| // queried as a store for aggregation-friendly cross-region queries (e.g. quantiles). | ||
| type storeEmulator struct { | ||
| storeapi.UnimplementedStoreApiServer |
There was a problem hiding this comment.
nit: I presume, we do not support async searches with regions enabled. It would be reasonable if we got some errors clarifying that. Currently, I get: can't find store shards in config when async search request is issued.
| // MaxConcurrent limits how many regions are queried in parallel per search (0 = no limit). | ||
| MaxConcurrent int `config:"max_concurrent" default:"0"` | ||
| // MaxRegionsPerRequest is the maximum number of regions a client can specify in one search request (e.g. 5). | ||
| MaxRegionsPerRequest int `config:"max_regions_per_request" default:"5"` |
There was a problem hiding this comment.
nit: Honestly, I would just remove these Max* options altogether. Yes, we'll have 300+ regions, but it seems like a manageable task to merge the results, especially once we have streaming single-phase search.
| ingestor, err := proxyapi.NewIngestor(pconfig, inMemory) | ||
| regionsCfg := cfg.Experimental.Regions | ||
| // without regions | ||
| if !config.UseRegions { |
There was a problem hiding this comment.
nit: For better readability, let's just extract this setup logic into a separate function, which in turn will call one of two functions - for the experimental config or for the traditional one.
func setupStores(cfg config.Config, pConfig proxyapi.IngestorConfig) proxyapi.IngestorConfig {
if !cfg.Experimental.Regions.UseRegions {
setupRegionStores(cfg, pConfig)
}
return setupTraditionalStores(cfg, pConfig)
}|
|
||
| FailPartialResponse = false | ||
|
|
||
| UseRegions = false |
There was a problem hiding this comment.
nit: I would avoid creating yet another global config variable. They create complications down the road (leaky abstraction).
Everywhere we use this variable, there is indirect data in the context about whether regions are enabled or not, so we can easily do without this variable.
| shards := make([][]string, 0) | ||
| vers := make([]string, 0) | ||
| for i, name := range si.config.RegionNames { | ||
| if _, ok := allowed[name]; ok && i < len(si.config.RegionStores.Shards) { |
There was a problem hiding this comment.
This is a very, very basic implementation. It's a good idea for a future enhancement.
|
|
||
| // TODO(moflotas) in case of regions work improperly | ||
| func (s *storeEmulator) Status(_ context.Context, _ *storeapi.StatusRequest) (*storeapi.StatusResponse, error) { | ||
| st := s.searchIngestor.Status(context.Background()) |
There was a problem hiding this comment.
Why is a new context being created instead of passing it from the parameters?
| // Regions maps region name to store API gRPC address (e.g. "eu" -> "eu-proxy:9004"). | ||
| Regions map[string]string `config:"regions"` | ||
| // MaxConcurrent limits how many regions are queried in parallel per search (0 = no limit). | ||
| MaxConcurrent int `config:"max_concurrent" default:"0"` |
There was a problem hiding this comment.
nit: Honestly, I would just remove these Max* options altogether. Yes, we'll have 300+ regions, but it seems like a manageable task to merge the results, especially once we have streaming single-phase search.
| // RegionStores is a separate topology for experimental.regions (not mixed with hot_stores). When set, search uses only this. | ||
| RegionStores *stores.Stores | ||
| // RegionNames maps shard index to region name (same order as RegionStores.Shards). | ||
| RegionNames []string |
There was a problem hiding this comment.
In my opinion, there are quite a few ugly spots in the proxy design right now.
This PR is the first prototype phase of the solution. We plan to introduce unified (non‑experimental) configs for describing cluster topology with region support.
I think it would be good, during the second phase, to also clean up the structures and entities in this part of the project.
Description
Fixes #363
If you have used LLM/AI assistance please provide model name and full prompt: